package com.example.video.common.util;

import Common.osSelect;
import com.example.video.HCNetSDK;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class HCNetTools {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    HCNetSDK hCNetSDK = null;

    boolean initFlag = createSDKInstance();

    //海康播放库(SDK里的PlayCtrl不是此处的PlayCtrl)
    //static PlayCtrl playControl = PlayCtrl.INSTANCE;

    private HCNetSDK.NET_DVR_WORKSTATE_V30 m_strWorkState;
    private HCNetSDK.NET_DVR_DEVICEINFO_V30 m_strDeviceInfo;//设备信息
    private HCNetSDK.NET_DVR_IPPARACFG m_strIpparaCfg;//IP参数
    private HCNetSDK.NET_DVR_CLIENTINFO m_strClientInfo;//用户参数

    private boolean bRealPlay;//是否在预览.
    private String m_sDeviceIP;//已登录设备的IP地址

    FileOutputStream outputStream;

    static boolean palybackFlay = false;

    static playDataCallBack playBackCallBack; //回放码流回调


    static FExceptionCallBack_Imp fExceptionCallBack;
    private int lUserID;//用户句柄
    static int lDChannel;  //预览通道号
    private int loadHandle;//下载句柄
    private int lPreviewHandle;//预览句柄
    private IntByReference m_lPort;//回调预览时播放库端口指针
    private IntByReference m_err;//错误号

    public HCNetTools() {
        lUserID = -1;
        lPreviewHandle = -1;
        m_lPort = new IntByReference(-1);
        m_err = new IntByReference(-1);
    }

    /**
     * 动态库加载
     *
     * @return
     */
    private boolean createSDKInstance() {
        if (hCNetSDK == null) {
            synchronized (HCNetSDK.class) {
                String strDllPath = "";
                try {
                    if (osSelect.isWindows())
                        //win系统加载库路径
                        strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";

                    else if (osSelect.isLinux())
                        //Linux系统加载库路径
                        strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so";
//                        strDllPath = "/opt/alarm/lib/libhcnetsdk.so";
                    hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
                } catch (Exception ex) {
                    System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 初始化资源配置
     */
    public int initDevices() {
        boolean b = hCNetSDK.NET_DVR_Init();
        if (!b) {
            //初始化失败
            System.out.println("初始化失败");
            //throw new RuntimeException("初始化失败");
            return 1;
        }
        hCNetSDK.NET_DVR_SetConnectTime(5000, 3);
        return 0;
    }

    /**
     * 设备注册
     *
     * @param name     设备用户名
     * @param password 设备登录密码
     * @param ip       IP地址
     * @param port     端口
     * @return 结果
     */
    public int deviceLogin(String name, String password, String ip, String port) {
        //判断当前是否在预览
        if (bRealPlay) {
            //"注册新用户请先停止当前预览";
            return 2;
        }
        //先注销,在登录
        if (lUserID > -1) {
            hCNetSDK.NET_DVR_Logout_V30(lUserID);
            lUserID = -1;
        }
        //注册(既登录设备)开始
        m_sDeviceIP = ip;
        short iPort = Integer.valueOf(port).shortValue();
        //获取设备参数结构
        m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();
        //登录设备
        lUserID = hCNetSDK.NET_DVR_Login_V30(ip, iPort, name, password, m_strDeviceInfo);
        long userID = lUserID;
        if (userID == -1) {
            int iErr = hCNetSDK.NET_DVR_GetLastError();
            logger.info("注册失败,错误号：{},错误信息:{}", iErr, hCNetSDK.NET_DVR_GetErrorMsg(m_err));
            //登录未成功,IP置为空
            //"注册失败";
            return 3;
        }
        return 0;
    }


    /**
     * 设备登录V40 与V30功能一致
     *
     * @param ip   设备IP
     * @param port SDK端口，默认设备的8000端口
     * @param user 设备用户名
     * @param psw  设备密码
     */
    public void login_V40(String ip, short port, String user, String psw) {

        //异常消息回调
        if (fExceptionCallBack == null) {
            fExceptionCallBack = new FExceptionCallBack_Imp();
        }
        Pointer pUser = null;
        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
            return;
        }

        System.out.println("设置异常消息回调成功");

        //启动SDK写日志
        hCNetSDK.NET_DVR_SetLogToFile(3, "./sdkLog", false);
        //注册
        HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息
        HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息

        String m_sDeviceIP = ip;//设备ip地址
        m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
        System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());

        String m_sUsername = user;//设备用户名
        m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
        System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());

        String m_sPassword = psw;//设备密码
        m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
        System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());

        m_strLoginInfo.wPort = port;
        m_strLoginInfo.bUseAsynLogin = false; //是否异步登录：0- 否，1- 是
        m_strLoginInfo.byLoginMode = 0;  //0- SDK私有协议，1- ISAPI协议
        m_strLoginInfo.write();

        lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
        if (lUserID == -1) {
            System.out.println("登录失败，错误码为" + hCNetSDK.NET_DVR_GetLastError());
            return;
        } else {
            System.out.println(ip + ":设备登录成功！");
            //相机一般只有一个通道号，热成像相机有2个通道号，通道号为1或1,2
            //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值
            if ((int) m_strDeviceInfo.struDeviceV30.byStartDChan == 1 && (int) m_strDeviceInfo.struDeviceV30.byStartDChan == 33) {
                //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值,NVR起始通道号一般是33或者1开始
                lDChannel = (int) m_strDeviceInfo.struDeviceV30.byStartDChan;
                System.out.println("预览起始通道号：" + lDChannel);
            }
            return;
        }
    }


    /**
     * 注销登陆
     */
    public void deviceLogout() {
        //如果在预览,先停止预览, 释放句柄
        if (lPreviewHandle > -1) {
            hCNetSDK.NET_DVR_StopRealPlay(lPreviewHandle);
        }
        //如果已经注册,注销
//        if (lUserID> -1) {
//            hCNetSDK.NET_DVR_Logout_V30(lUserID);
//            logger.info("注销成功");
//        }
//        if (hCNetSDK.NET_DVR_Logout(lUserID)) {
//            System.out.println("注销成功");
//        }

        hCNetSDK.NET_DVR_Logout(lUserID);
        //SDK反初始化，释放资源，只需要退出时调用一次
        hCNetSDK.NET_DVR_Cleanup();
        logger.info("清理释放资源");
        return;

    }

    /**
     * 获取设备通道
     */
    public List<String> getChannelNumber() {
        List<String> channelList = new ArrayList<>();
        //获取IP接入配置参数
        IntByReference ibrBytesReturned = new IntByReference(0);
        boolean bRet = false;
        m_strIpparaCfg = new HCNetSDK.NET_DVR_IPPARACFG();
        m_strIpparaCfg.write();
        Pointer lpIpParaConfig = m_strIpparaCfg.getPointer();
        bRet = (m_strDeviceInfo.byIPChanNum != 0);

        String devices = "";
        if (!bRet) {
            logger.info("设备不支持,则表示没有IP通道");
            //设备不支持,则表示没有IP通道
            for (int iChannum = 0; iChannum < m_strDeviceInfo.byChanNum; iChannum++) {
                devices = "Camera" + (iChannum + m_strDeviceInfo.byStartChan);
                channelList.add(devices);
            }
        } else {
            //设备支持IP通道
            boolean ok = hCNetSDK.NET_DVR_GetDVRConfig(lUserID, HCNetSDK.NET_DVR_GET_IPPARACFG, 0, lpIpParaConfig, m_strIpparaCfg.size(), ibrBytesReturned);
            if (!ok) {
                String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
                logger.info("获取配置失败：" + m_err.getValue() + "," + err);
            }
            m_strIpparaCfg.read();

            for (int iChannum = 0; iChannum < m_strDeviceInfo.byChanNum; iChannum++) {
                if (m_strIpparaCfg.byAnalogChanEnable[iChannum] == 1) {
                    devices = "Camera" + (iChannum + m_strDeviceInfo.byStartChan);
                    channelList.add(devices);
                }
            }

            for (int iChannum = 0; iChannum < HCNetSDK.MAX_IP_CHANNEL; iChannum++) {
                if (m_strIpparaCfg.struIPChanInfo[iChannum].byEnable == 1) {
                    devices = "IP" + (hCNetSDK.NET_DVR_SDKChannelToISAPI(lUserID, iChannum + m_strDeviceInfo.byStartDChan, true));
                    String channelIP = (new String(m_strIpparaCfg.struIPDevInfo[iChannum].struIP.sIpV4));
                    channelIP = channelIP.trim();
                    devices = devices + "," + channelIP;
                    channelList.add(devices);
                }
            }
        }

        return channelList;
    }


    /**
     * 下载录像
     *
     * @return boolean
     */
    public boolean downloadBack(LocalDateTime startTime, LocalDateTime endTime, String filePath, int channel) throws InterruptedException {
        loadHandle = -1;
        loadHandle = hCNetSDK.NET_DVR_GetFileByTime(lUserID, channel, getHkTime(startTime), getHkTime(endTime), filePath);
        if (loadHandle >= 0) {
            //开始下载
            boolean downloadFlag = hCNetSDK.NET_DVR_PlayBackControl(loadHandle, HCNetSDK.NET_DVR_PLAYSTART, 0, null);
            int tmp = -1;
            IntByReference pos = new IntByReference(0);
            while (loadHandle >= 0) {
                boolean backFlag = hCNetSDK.NET_DVR_PlayBackControl(loadHandle, HCNetSDK.NET_DVR_PLAYGETPOS, 0, pos);
                //防止单个线程死循环
                if (!backFlag) {
                    return downloadFlag;
                }
                int produce = pos.getValue();
                //输出进度
                if ((produce % 10) == 0 && tmp != produce) {
                    tmp = produce;
                    logger.info("视频下载进度:" + "==" + produce + "%");
                }
                //下载成功
                if (produce == 100) {
                    hCNetSDK.NET_DVR_StopGetFile(loadHandle);
                    loadHandle = -1;
                    String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
                    logger.info("下载结束,last error: " + m_err.getValue() + "," + err);
                    return true;
                }
                //下载失败
                if (produce > 100) {
                    hCNetSDK.NET_DVR_StopGetFile(loadHandle);
                    loadHandle = -1;
                    String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
                    logger.warn("下载异常终止!错误号：{} 错误原因:{}", m_err.getValue(), err);
                    return false;
                }
                Thread.sleep(500);
            }
            String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
            logger.warn("下载异常终止!错误号：{} 错误原因:{}:", m_err.getValue(), err);
            return false;
        } else {
            String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
            logger.warn("获取录像文件错误!错误号：{} 错误原因:{}:", m_err.getValue(), err);
            return false;
        }
    }

    /**
     * 获取录像文件信息
     *
     * @Return：
     */
    public List<HashMap<String, String>> getVideoFileList(LocalDateTime startTime, LocalDateTime endTime, int channel) {
        List<HashMap<String, String>> fileList = new ArrayList<>();

        // 搜索条件
        HCNetSDK.NET_DVR_FILECOND m_strFilecond = new HCNetSDK.NET_DVR_FILECOND();

        m_strFilecond.struStartTime = getHkTime(startTime);
        m_strFilecond.struStopTime = getHkTime(endTime);
        m_strFilecond.lChannel = channel;//通道号

        int lFindFile = hCNetSDK.NET_DVR_FindFile_V30(lUserID, m_strFilecond);
        HCNetSDK.NET_DVR_FINDDATA_V30 strFile = new HCNetSDK.NET_DVR_FINDDATA_V30();
        if (lFindFile < 0) {
            String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
            logger.warn("获取录像文件错误!错误号：{} 错误原因:{}:", m_err.getValue(), err);
            return null;
        }
        int lnext;
        strFile = new HCNetSDK.NET_DVR_FINDDATA_V30();
        HashMap<String, String> map = new HashMap<>();
        boolean flag = true;
        while (flag) {
            lnext = hCNetSDK.NET_DVR_FindNextFile_V30(lFindFile, strFile);
            if (lnext == HCNetSDK.NET_DVR_FILE_SUCCESS) {
                //搜索成功
                //添加文件名信息
                String[] s = new String[2];
                s = new String(strFile.sFileName).split("\0", 2);
                map.put("fileName:", new String(s[0]));

                int iTemp;
                String MyString;
                if (strFile.dwFileSize < 1024 * 1024) {
                    iTemp = (strFile.dwFileSize) / (1024);
                    MyString = iTemp + "K";
                } else {
                    iTemp = (strFile.dwFileSize) / (1024 * 1024);
                    MyString = iTemp + "M   ";
                    iTemp = ((strFile.dwFileSize) % (1024 * 1024)) / (1204);
                    MyString = MyString + iTemp + "K";
                }
                map.put("fileSize", MyString);                      //添加文件大小信息
                map.put("struStartTime", strFile.struStartTime.toStringTime());                      //添加开始时间信息
                map.put("struStopTime", strFile.struStopTime.toStringTime());                      //添加结束时间信息
                fileList.add(map);
            } else {
                if (lnext == HCNetSDK.NET_DVR_ISFINDING) {//搜索中
                    //System.out.println("搜索中");
                    continue;
                } else {
                    flag = false;
                    if (lnext == HCNetSDK.NET_DVR_FILE_NOFIND) {
                        //flag=false;
                    } else {
                        //flag=false;
                        System.out.println("搜索文件结束");
                        logger.info("搜索文件结束");
                        boolean flag2 = hCNetSDK.NET_DVR_FindClose_V30(lFindFile);
                        if (flag2 == false) {
                            logger.info("结束搜索失败");
                            System.out.println("结束搜索失败");
                        }
                    }
                }
            }
        }
        return fileList;
    }


    /**
     * 获取海康录像机格式的时间
     *
     * @param time
     * @return
     */
    public HCNetSDK.NET_DVR_TIME getHkTime(LocalDateTime time) {
        HCNetSDK.NET_DVR_TIME structTime = new HCNetSDK.NET_DVR_TIME();

        String str = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss").format(time);
        String[] times = str.split("-");
        structTime.dwYear = Integer.parseInt(times[0]);
        structTime.dwMonth = Integer.parseInt(times[1]);
        structTime.dwDay = Integer.parseInt(times[2]);
        structTime.dwHour = Integer.parseInt(times[3]);
        structTime.dwMinute = Integer.parseInt(times[4]);
        structTime.dwSecond = Integer.parseInt(times[5]);
        return structTime;
    }

    /**
     * 获取海康录像机格式的时间
     *
     * @param time
     * @return
     */
    public HCNetSDK.NET_DVR_TIME getHkTime(String time) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime parse = LocalDateTime.parse(time, formatter);
        return getHkTime(parse);
    }
    public HCNetSDK.NET_DVR_TIME_V30 getHkTimeV30(String time) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime parse = LocalDateTime.parse(time, formatter);
        return getHkTimeV30(parse);
    }

    /**
     * 获取海康录像机格式的时间
     *
     * @param time
     * @return
     */
    public HCNetSDK.NET_DVR_TIME_V30 getHkTimeV30(LocalDateTime time) {
        HCNetSDK.NET_DVR_TIME_V30 structTime = new HCNetSDK.NET_DVR_TIME_V30();

        String str = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss").format(time);
        String[] times = str.split("-");
        structTime.wYear = Short.parseShort(times[0]);
        structTime.byMonth = Byte.parseByte(times[1]);
        structTime.byDay = Byte.parseByte(times[2]);
        structTime.byHour = Byte.parseByte(times[3]);
        structTime.byMinute = Byte.parseByte(times[4]);
        structTime.bySecond = Byte.parseByte(times[5]);
        return structTime;
    }


    static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
        public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
            System.out.println("异常事件类型:" + dwType);
            return;
        }
    }



    class playDataCallBack implements HCNetSDK.FPlayDataCallBack {
        public void invoke(int lPlayHandle, int dwDataType, Pointer pBuffer, int dwBufSize, int dwUser) {
            System.out.println("回放码流回调...");
            //将设备发送过来的回放码流数据写入文件
            long offset = 0;
            ByteBuffer buffers = pBuffer.getByteBuffer(offset, dwBufSize);
            byte[] bytes = new byte[dwBufSize];
            buffers.rewind();
            buffers.get(bytes);
            try {
                outputStream.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
